using System;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Text;
using System.Collections.Generic;
using System.Threading;

namespace ProjectNocturnalFool
{

    /// <summary>
    /// Enum used for various buttons.
    /// </summary>
    public enum Input
    {
        Start,
        Reject,
        One,
        Two,
        Three,
        Four,
        Up,
        Down,
        Left,
        Right
    }

    /// <summary>
    /// Enum used for various mouse clicks.
    /// </summary>
    public enum Click
    {
        Left,
        Right,
        Middle
    }


    /// <summary>
    /// Helper for reading input from keyboard, gamepad. This class 
    /// tracks both the current and previous state of the input devices, and implements 
    /// query methods for high level input actions such as "move up through the menu"
    /// or "pause the game".
    /// </summary>
    public sealed class InputMgr
    {
        #region Fields
        public const int MAX_PAYNE = 4;
        
        public const int MAX_INPUTS = MAX_PAYNE;

        public readonly KeyboardState[] CurrentKeyboardStates;
        public readonly GamePadState[] CurrentGamePadStates;
        private MouseState CurrentMouseStates;

        public readonly KeyboardState[] LastKeyboardStates;
        public readonly GamePadState[] LastGamePadStates;
        public MouseState LastMouseStates;

        public readonly List<Dictionary<Input, Keys>> PlayerKeyConfigs;
        public readonly List<Dictionary<Input, Buttons>> PlayerButtonConfigs;

        public readonly bool[] GamePadWasConnected;

        public List<char> KeyboardBuffer = new List<char>();
        //HACK
        public string message = "";

        #endregion

       
        #region Initialization

        /// <summary>
        /// Allocate ourselves.
        /// We have a private constructor, so no one else can.
        /// </summary>
        static readonly InputMgr _instance = new InputMgr();

        /// <summary>
        /// Access SiteStructure.Instance to get the singleton object.
        /// Then call methods on that instance.
        /// </summary>
        public static InputMgr Instance
        {
            get { return _instance; }
        }

        /// <summary>
        /// Constructs a new input state.
        /// </summary>
        private InputMgr()
        {
            CurrentKeyboardStates = new KeyboardState[MAX_INPUTS];
            CurrentGamePadStates = new GamePadState[MAX_INPUTS];
            CurrentMouseStates = new MouseState();

            LastKeyboardStates = new KeyboardState[MAX_INPUTS];
            LastGamePadStates = new GamePadState[MAX_INPUTS];
            LastMouseStates = new MouseState();

            PlayerKeyConfigs = new List<Dictionary<Input, Keys>>();
            PlayerButtonConfigs = new List<Dictionary<Input, Buttons>>();
            for (int i = 0; i < MAX_INPUTS; i++)
            {
                PlayerKeyConfigs.Add(new Dictionary<Input, Keys>());
                PlayerButtonConfigs.Add(new Dictionary<Input, Buttons>());
                //HACK
                //Some Action Mapping
                PlayerKeyConfigs[i].Add(Input.One, Keys.D1);
                PlayerKeyConfigs[i].Add(Input.Two, Keys.D2);
                PlayerKeyConfigs[i].Add(Input.Three, Keys.D3);
                PlayerKeyConfigs[i].Add(Input.Four, Keys.D4);
                PlayerKeyConfigs[i].Add(Input.Start, Keys.Enter);
                PlayerKeyConfigs[i].Add(Input.Reject, Keys.Escape);
                PlayerKeyConfigs[i].Add(Input.Up, Keys.Up);
                PlayerKeyConfigs[i].Add(Input.Down, Keys.Down);
                PlayerKeyConfigs[i].Add(Input.Left, Keys.Left);
                PlayerKeyConfigs[i].Add(Input.Right, Keys.Right);
                //PlayerKeyConfigs[i].Add(Input.Up, Keys.W);
                //PlayerKeyConfigs[i].Add(Input.Down, Keys.S);
                //PlayerKeyConfigs[i].Add(Input.Left, Keys.A);
                //PlayerKeyConfigs[i].Add(Input.Right, Keys.D);

                //Game Pad Mapping
                PlayerButtonConfigs[i].Add(Input.One, Buttons.Start);
                PlayerButtonConfigs[i].Add(Input.Up, Buttons.DPadUp);
                PlayerButtonConfigs[i].Add(Input.Down, Buttons.DPadDown);
                PlayerButtonConfigs[i].Add(Input.Reject, Buttons.A);
                PlayerButtonConfigs[i].Add(Input.Start, Buttons.B);
            }
            GamePadWasConnected = new bool[MAX_INPUTS];

            EventInput.Initialize(TacticsGame.Instance.Window);
            EventInput.CharEntered += new CharEnteredHandler(EventInput_CharEntered);


        }

        #endregion

        #region Public Methods


        /// <summary>
        /// Reads the latest state of the keyboard and gamepad.
        /// </summary>
        public void Update()
        {
            LastMouseStates = CurrentMouseStates;
            CurrentMouseStates = Mouse.GetState();

            for (int i = 0; i < MAX_INPUTS; i++)
            {
                LastKeyboardStates[i] = CurrentKeyboardStates[i];
                LastGamePadStates[i] = CurrentGamePadStates[i];
                

                CurrentKeyboardStates[i] = Keyboard.GetState((PlayerIndex)i);
                CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);
               
                
                // Keep track of whether a gamepad has ever been
                // connected, so we can detect if it is unplugged.
                if (CurrentGamePadStates[i].IsConnected)
                {
                    GamePadWasConnected[i] = true;
                }
            }
        }


        /// <summary>
        /// Helper for checking if a key was newly pressed during this update. The
        /// controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a keypress
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        private bool IsNewKeyPress(Keys key, PlayerIndex controllingPlayer)
        {
            return (CurrentKeyboardStates[(int)controllingPlayer].IsKeyDown(key) &&
                        LastKeyboardStates[(int)controllingPlayer].IsKeyUp(key));

        }


        /// <summary>
        /// Helper for checking if a button was newly pressed during this update.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a button press
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        private bool IsNewButtonPress(Buttons button, PlayerIndex controllingPlayer)
        {
                return (CurrentGamePadStates[(int)controllingPlayer].IsButtonDown(button) &&
                        LastGamePadStates[(int)controllingPlayer].IsButtonUp(button) &&
                        CurrentGamePadStates[(int)controllingPlayer].IsConnected);

        }


        /// <summary>
        /// Checks if a key or button press has been made for the first time.
        /// </summary>
        public bool IsButtonPress(PlayerIndex controllingPlayer, Input input)
        {

                Keys checkKey = Keys.None;
                Buttons checkButton = Buttons.BigButton;

                PlayerKeyConfigs[(int)controllingPlayer].TryGetValue(input, out checkKey);
                PlayerButtonConfigs[(int)controllingPlayer].TryGetValue(input, out checkButton);

                return IsNewKeyPress(checkKey, controllingPlayer) ||
                  IsNewButtonPress(checkButton, controllingPlayer);
        
        }

        /// <summary>
        /// Checks if a key or button press has been made for the first time.
        /// </summary>
        public bool IsButtonHeld(PlayerIndex controllingPlayer, Input input)
        {

                Keys checkKey = Keys.None;
                Buttons checkButton = Buttons.BigButton;

                PlayerKeyConfigs[(int)controllingPlayer].TryGetValue(input, out checkKey);
                PlayerButtonConfigs[(int)controllingPlayer].TryGetValue(input, out checkButton);

                return CurrentKeyboardStates[(int)controllingPlayer].IsKeyDown(checkKey) ||
                        (CurrentGamePadStates[(int)controllingPlayer].IsButtonDown(checkButton) && CurrentGamePadStates[(int)controllingPlayer].IsConnected);
          
        }

        /// <summary>
        /// Checks if a mouse button is clicked, if the clickType is null, only the x and y coordinates are output.
        /// </summary>
        /// <param name="clickType"> The button checked </param>
        public bool isMouseClickUp(Click clickType)
        {
                ButtonState mouseState = clickType == Click.Left ? CurrentMouseStates.LeftButton :
                    clickType == Click.Right ? CurrentMouseStates.RightButton : CurrentMouseStates.RightButton;

                ButtonState previousMouseState = clickType == Click.Left ? LastMouseStates.LeftButton :
                    clickType == Click.Right ? LastMouseStates.RightButton : LastMouseStates.RightButton; 
                
                if (mouseState != ButtonState.Pressed
                    && previousMouseState == ButtonState.Pressed)
                {
                    return true;
                }
                return false;
        }

        /// <summary>
        /// Checks if a mouse button is clicked, if the clickType is null, only the x and y coordinates are output.
        /// </summary>
        /// <param name="clickType"> The button checked </param>
        public bool isMouseClickDown(Click clickType)
        {
            ButtonState mouseState = clickType == Click.Left ? CurrentMouseStates.LeftButton :
                clickType == Click.Right ? CurrentMouseStates.RightButton : CurrentMouseStates.RightButton;

            ButtonState previousMouseState = clickType == Click.Left ? LastMouseStates.LeftButton :
                clickType == Click.Right ? LastMouseStates.RightButton : LastMouseStates.RightButton;

            if (mouseState == ButtonState.Pressed
                && previousMouseState != ButtonState.Pressed)
            {
                return true;
            }
            return false;
        }


        /// <summary>
        /// Checks if a mouse button is clicked, if the clickType is null, only the x and y coordinates are output.
        /// </summary>
        /// <param name="clickType"> The button checked </param>
        public bool isMouseHeld(Click clickType)
        {
                ButtonState mouseState = clickType == Click.Left ? CurrentMouseStates.LeftButton :
                    clickType == Click.Right ? CurrentMouseStates.RightButton : CurrentMouseStates.RightButton;

                if (mouseState == ButtonState.Pressed)
                {
                    return true;
                }

            return false;
        }

        /// <summary>
        /// Gets the mouse information
        /// </summary>
        /// <param name="x">Output of the x position of the mouse</param>
        /// <param name="y">Output of the y position of the mouse</param>
        public void getMouse(out int x, out int y)
        {
            int borderX = VirtualScreen.Instance.area.X;
            int borderY = VirtualScreen.Instance.area.Y ;

            float ratioX;
            float ratioY;
            
            if (CurrentMouseStates.X < borderX)
                ratioX = 0;
            else if (CurrentMouseStates.X >  VirtualScreen.Instance.PhysicalWidth - borderX)
                ratioX = VirtualScreen.Instance.VirtualWidth;
            else
                 ratioX = ((float)VirtualScreen.Instance.VirtualWidth / (VirtualScreen.Instance.PhysicalWidth - borderX * 2)) * (CurrentMouseStates.X - borderX);
            

            if (CurrentMouseStates.Y < borderY)
                ratioY = 0;
            else if (CurrentMouseStates.Y >  VirtualScreen.Instance.PhysicalHeight - borderY)
                ratioY = VirtualScreen.Instance.VirtualHeight;
            else
                 ratioY = ((float)VirtualScreen.Instance.VirtualHeight / (VirtualScreen.Instance.PhysicalHeight - borderY * 2)) * (CurrentMouseStates.Y - borderY );


            x = (int)ratioX;
            y = (int)ratioY;
        }

        /// <summary>
        /// Gets if the mouse has moved since the last update
        /// </summary>
        /// <returns>True if the mouse has changed position, false otherwise</returns>
        public bool isMouseMoved()
        {
            return (CurrentMouseStates.X != LastMouseStates.X
                || CurrentMouseStates.Y != LastMouseStates.Y);
        }

        #endregion

        #region Keypress Handler
        void EventInput_CharEntered(object sender, CharacterEventArgs e)
        {
            KeyboardBuffer.Add(e.Character);   
        }
        
      
        #endregion
    }
}
